/*
 * Copyright 2014 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <memory>
#include <type_traits>
#include <native_drawing/drawing_record_cmd.h>

#include "include/core/SkData.h"
#include "include/core/SkDrawable.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkTypes.h"
#include "oh/OHDrawingAPI.h"
#include "src/core/SkBigPicture.h"
#include "src/core/SkRecord.h"
#include "src/core/SkRecordDraw.h"
#include "src/core/SkRecordOpts.h"
#include "src/core/SkRecordedDrawable.h"
#include "src/core/SkRecorder.h"
#include "src/core/SkTraceEvent.h"
#include "src/core/SkTraceEventCommon.h"


using namespace skia_private;

SkPictureRecorder::SkPictureRecorder() {
    fActivelyRecording = false;
    fOHRecorder = OH_Drawing_RecordCmdUtilsCreate();
    fNowOHNode = OHRenderNode::CreateNormalNode();
}

SkPictureRecorder::~SkPictureRecorder() {
    OH_Drawing_RecordCmdUtilsDestroy(fOHRecorder);
    if (fRecordCanvas) {
        delete fRecordCanvas;
    }
}

SkCanvas* SkPictureRecorder::beginRecording(const SkRect& userCullRect,
                                            sk_sp<SkBBoxHierarchy> bbh) {
    const SkRect cullRect = userCullRect.isEmpty() ? SkRect::MakeEmpty() : userCullRect;
    if (fRecordCanvas) {
        return nullptr;
    }
    OH_Drawing_Canvas *ohCanvas = nullptr;
    // this width/height may effect the cull result get by OH_Drawing_CanvasGetLocalClipBounds
    OH_Drawing_RecordCmdUtilsBeginRecording(fOHRecorder, cullRect.width(), cullRect.height(), &ohCanvas);
    if (!ohCanvas) {
        return nullptr;
    }
    
    if (fNowOHNode == nullptr || fNowOHNode->getPicture() != nullptr) {
        fNowOHNode = OHRenderNode::CreateNormalNode();
    }
    
    bool needHash = false;
    
    fCullRect = cullRect;
    if (fNowOHNode) {
        needHash = (fNowOHNode->getPaintChildrenNum() != 0 || fNowOHNode->getPictureChildrenNum() != 0);
        fNowOHNode->restartChildAdd();
        fNowOHNode->addRecordCnt();
    }
    fRecordCanvas = new SkCanvas(ohCanvas, fNowOHNode.get());
    fRecordCanvas->setRecordCull(cullRect);
    if (fNowOHNode) {
        fRecordCanvas->setMaxDeviceFrame(fNowOHNode->getMaxDeviceFrame());
    }
    fRecordCanvas->enableHash(needHash);
    
    fActivelyRecording = true;
    return fRecordCanvas;
}

SkCanvas* SkPictureRecorder::beginRecording(const SkRect& bounds, SkBBHFactory* factory) {
    return this->beginRecording(bounds, factory ? (*factory)() : nullptr);
}

SkCanvas* SkPictureRecorder::getRecordingCanvas() {
    return fActivelyRecording ? fRecordCanvas : nullptr;
}

class SkEmptyPicture final : public SkPicture {
public:
    void playback(SkCanvas*, AbortCallback*) const override { }

    size_t approximateBytesUsed() const override { return sizeof(*this); }
    int    approximateOpCount(bool nested)   const override { return 0; }
    SkRect cullRect()             const override { return SkRect::MakeEmpty(); }
};

class SkOHPicture final : public SkPicture {
public:
    explicit SkOHPicture(SkRect &cull, bool noLimit, OH_Drawing_RecordCmd *cmd, std::shared_ptr<OHRenderNode> node,
                         std::vector<sk_sp<SkPicture>> &&subPics, float cost, bool disableNode, uint64_t hash)
        : fCull(cull), fNoLimitDraw(noLimit), fDisableNode(disableNode), fHash(hash) {
//        // used for debug
//        char trace_str[200];
//        snprintf(trace_str, 199, "picture-create-loc-%.0f,%.0f-size-%.0f,%.0f-%p", fCull.x(), fCull.y(), fCull.width(), fCull.height(), node.get());
//        TRACE_EVENT0("skia", TRACE_STR_COPY(trace_str));
        fOHRecordCmd = OHDrawingRecordCmdSPtr(cmd, [](OH_Drawing_RecordCmd *cmd) {
            if (cmd) {
                OH_Drawing_RecordCmdDestroy(cmd);
            }
        });
        
        fSubPics = std::move(subPics);
        fDrawCostEstimate = cost;
        if (node) {
            fOriginNode = node;
            if (!fOriginNode->isVisible()) {
                fUnusedCloneNodes.push_back(fOriginNode);
            }
            fCacheCloneNodes.push_back(fOriginNode);
            fOriginNode->updatePicture(this);
            if (node->getPaintChildrenNum() > 0) {
                fCanPlaybackInPicture = false;
            }
            fPlaybackInNode = node->isInNode();
        }
    }
    ~SkOHPicture() {
//        // used for debug
//        char trace_str[200];
//        snprintf(trace_str, 199, "picture-release-loc-%.0f,%.0f-size-%.0f,%.0f-%p", fCull.x(), fCull.y(), fCull.width(),
//                 fCull.height(), fOriginNode.get());
//        TRACE_EVENT0("skia", TRACE_STR_COPY(trace_str));
        for (auto &node : fCacheCloneNodes) {
            node->updatePicture(nullptr);
        }
    }
    
    void playback(SkCanvas* canvas, AbortCallback* callback) const override {
        if (callback && callback->abort()) {
            return;
        }
        if (canvas) {
            auto canvas_node = canvas->getRenderNode();
            SkMatrix father_matrix;
            SkClipShape device_clip_shape;
            SkRect device_clip_rect = SkRect::MakeEmpty();
            bool should_paint_in_picture = false;
            SkRect father_frame;
            SkRect device_area;
            std::optional<SkRect> paint_area = std::nullopt;
            if (!fNowCacheNode && fOriginNode) {
                fNowCacheNode = generateNewNode();
            }
            
            father_matrix = canvas->getTotalMatrix();
            device_clip_shape = canvas->getDeviceClipShape();

            if (!canvas_node || !fOriginNode || canvas->isForceDrawInPicture() ||
                (canvas->isInSaveLayer() && OHDrawingAPI::support_OH_Drawing_CanvasDrawRecordCmdNesting)) {
                should_paint_in_picture = true;
            } else {
                
                auto father_paint_area = canvas_node->getPaintChildArea();
                father_frame = canvas_node->getFrameSize();
                if (fNoLimitDraw) {
                    device_area = father_frame;
                } else {
                    father_matrix.mapRect(&device_area, fCull);
                }
                
                if (!std::holds_alternative<std::monostate>(device_clip_shape)) {
                    device_clip_rect = GetClipShapeBounds(device_clip_shape);
                    paint_area = SkRect::MakeEmpty();
                    if (!fNoLimitDraw && !paint_area.value().intersect(device_area, device_clip_rect)) {
                    } else if (fNoLimitDraw) {
                        paint_area = device_clip_rect;
                    }
                } else {
                    if (!fNoLimitDraw) {
                        paint_area = device_area;
                    } else {
                        // If canvas has no child, this node can be drawn in father node directly.
                        // Otherwise, this node must be drawn in node(paint_area don't have value).
                        if (canvas_node->getPaintChildrenNum() == 0) {
                            should_paint_in_picture = true;
                        }
                    }
                }
                
                SkRect temp;
                // suit for painting in picture
                if (fCanPlaybackInPicture && !fPlaybackInNode && !should_paint_in_picture &&
                    OHDrawingAPI::support_OH_Drawing_CanvasDrawRecordCmdNesting) {
                    bool matrix_changed = fNowCacheNode->updateFatherMatrix(father_matrix);
                    // SkPicture doesn't change frequently => need be separated to a single node
                    bool record_delta_changed = (fOriginNode->updateDeltaChangedCnt(canvas_node->getRecordCnt()) >= 3);
                    // canvas_node->getAllNodeChildrenNum()>0 => may has brother node
                    bool need_draw_in_node =
                        (matrix_changed || record_delta_changed || canvas_node->getAllNodeChildrenNum() > 0);

                    // no paint area intersect -> can be drawn in the father node.
                    bool node_can_draw_in_father = (paint_area.has_value() && father_paint_area.has_value() &&
                                                    !(temp.intersect(father_paint_area.value(), paint_area.value()) &&
                                                      temp.width() > 0.1f && temp.height() > 0.1f));

                    if (!need_draw_in_node || (node_can_draw_in_father && canvas_node->needDelayChildAppend())) {
                        should_paint_in_picture = true;
                    }
                }
            }
            
            if (should_paint_in_picture) {
                if (canvas_node) {
                    canvas_node->addPictureChildrenNum();
                }
                if (canvas->hashEnabled()) {
                    canvas->updateHash(father_matrix.hash());
                    canvas->updateHash(GetClipShapeHash(device_clip_shape));
                    canvas->updateHash(GetClipShapeHash(canvas->getPendingDifferenceShape()));
                }
                if (fNowCacheNode) {
                    uint64_t hash = fNowCacheNode->pictureDraw(*canvas, true);
                    if (canvas->hashEnabled()) {
                        canvas->updateHash(hash);
                    }
                } else {
                    OHDrawingAPI::OH_Drawing_CanvasDrawRecordCmdNesting(*canvas, fOHRecordCmd.get());
                    if (canvas->hashEnabled()) {
                        canvas->updateHash(fHash);
                    }
                }
                fPlaybackInNode = false;
                canvas->addDrawCost(fDrawCostEstimate);
            } else {
                TRACE_EVENT1("skia", "playback-in-node", "id", fNowCacheNode->fNodeId);
                // update node size
                fNowCacheNode->setParent(canvas_node);
                
                if (canvas->hasClip()) {
                    auto cull_shape = canvas->getLocalClipShape();
                    if (!std::holds_alternative<std::monostate>(cull_shape)) {
                        fNowCacheNode->updateClipArea(cull_shape);
                    }
                } else {
                    fNowCacheNode->unsetClipArea();
                    if (canvas->hasPendingDifferenceShape()) {
                        fNowCacheNode->updatePendingDiffShape(canvas->getPendingDifferenceShape());
                    }
                }
                
                fNowCacheNode->updateDeviceClip(device_clip_shape);
                fNowCacheNode->updateFatherMatrix(father_matrix);
                fNowCacheNode->setPaintArea(paint_area);
                
                canvas_node->appendChild(fNowCacheNode);
                
                if (fNowCacheNode->isForbiddenRasterCache()) {
                    canvas->markForbidRasterCache();
                }
                canvas->addDrawCost(fNowCacheNode->getDrawCost());
                fNowCacheNode = nullptr;
                
                fPlaybackInNode = true;
            }
            fPlaybackCnt++;
            
//            // used for debug
//            SkPaint paint;
//            paint.setColor(SkColorSetARGB(255, 255, 0, 0));
//            canvas->drawRect(fCull, paint);
//            char cntString[10];
//            snprintf(cntString, 9, "%d", fPlaybackCnt);
//            SkFont font;
//            font.setSize(20.0f);
//            canvas->disableSubCanvas();
//            canvas->drawSimpleText(cntString, strlen(cntString), SkTextEncoding::kUTF8, 0, 30, font, paint);
        }
    }
    
    std::shared_ptr<OHRenderNode> generateNewNode() const {
//        TRACE_EVENT1("skia", "generateNewNode", "cachedNum", fCacheCloneNodes.size());
        if (fUnusedCloneNodes.size() == 0) {
            if (fCacheCloneNodes.size() > 10) {
                char trace_str[200];
                snprintf(trace_str, 199, "node-big-cache-size-%d-%.0f,%.0f-size-%.0f,%.0f-%p",
                         (int)fCacheCloneNodes.size(), fCull.x(), fCull.y(), fCull.width(), fCull.height(),
                         fOriginNode.get());
                TRACE_EVENT0("skia", TRACE_STR_COPY(trace_str));
                SkDebugf("%s", trace_str);
            }
            auto node = fOriginNode->makeClone();
            return node;
        } else {
            auto node = fUnusedCloneNodes.front();
            fUnusedCloneNodes.pop_front();
            return node;
        }
    }

    void recycleNode(std::shared_ptr<OHRenderNode> node) override {
//        TRACE_EVENT1("skia", "recycleNode", "cachedNum", fUnusedCloneNodes.size());
        fUnusedCloneNodes.push_back(std::move(node));
    }
    
    void cacheNode(std::shared_ptr<OHRenderNode> node) override {
        fCacheCloneNodes.push_back(node);
    }
    
    uint64_t hash() const override  { return fHash; }
    
    size_t approximateBytesUsed() const override { return sizeof(*this); }
    int    approximateOpCount(bool nested)   const override { return 1; }
    SkRect cullRect()             const override { return fCull; }
    bool noLimitDraw() const override { return fNoLimitDraw; }
    bool isInNode() const override { return fPlaybackInNode; }
    float getDrawCostEstimate() const override {
        return fDrawCostEstimate;
    }
    
    void setDrawCostEstimate(float cost) override { 
        fDrawCostEstimate = cost;
    }
    
    mutable bool fPlaybackInNode = false;
    mutable long fPlaybackTimestamp[10] = {};
    mutable int fLastTimestampIndex = 0;
    
    bool fCanPlaybackInPicture = true;
    bool fNoLimitDraw = true;
    bool fDisableNode = false;
    uint64_t fHash = 0;
    mutable int fPlaybackCnt = 0;
    SkRect fCull = {};
    mutable std::shared_ptr<OHRenderNode> fNowCacheNode;
    std::shared_ptr<OHRenderNode> fOriginNode;
    mutable std::list<std::shared_ptr<OHRenderNode>> fCacheCloneNodes;
    mutable std::list<std::shared_ptr<OHRenderNode>> fUnusedCloneNodes;
};

sk_sp<SkPicture> SkPictureRecorder::finishRecordingAsPicture() {
    fActivelyRecording = false;
    if (!fRecordCanvas) {
        return nullptr;
    }
    if (fRecordCanvas->fSubCanvas) {
        fRecordCanvas->finishSubCanvas();
    }
    if (fNowOHNode) {
        fNowOHNode->finishChildAdd();
    }
    
    OH_Drawing_RecordCmd* recordCmd = nullptr;
    
//    if (fRecordCanvas->hasPendingDifferenceShape()) {
//        fNowOHNode->setPendingDifferenceShape(fRecordCanvas->getPendingDifferenceShape());
//    }

    auto paintArea = fRecordCanvas->getFinishDrawBounds();
    bool noLimitDraw = false;
    if (paintArea.has_value()) {
        // SkDebugf("picture draw area(limit %d): %f %f %f %f", paintArea.has_value(), paintArea->x(), paintArea->y(), paintArea->width(), paintArea->height());
        if (fNowOHNode) {
            fNowOHNode->setRealFrame(paintArea.value(), false);
        }
        fCullRect = paintArea.value();
        noLimitDraw = false;
    } else {
        auto noLimtRect = SkRect::MakeWH(NODE_SIZE_ALIGNMENT, NODE_SIZE_ALIGNMENT);
        if (fNowOHNode) {
            fNowOHNode->setRealFrame(noLimtRect, true);
        }
        noLimitDraw = true;
    }
    
    uint64_t pictureHash = fRecordCanvas->hash();
    if (!fRecordCanvas->hashEnabled()) {
        DrawingHash hasher((uint64_t)fNowOHNode.get());
        hasher.push(DrawingHash::RunningUniqueId());
        pictureHash = hasher.digest();
    }
    TRACE_EVENT2("skia", __FUNCTION__, "id", fNowOHNode->fNodeId, "hash", pictureHash);
    
    if (fNowOHNode) {
        fNowOHNode->markForbidRasterCache(fRecordCanvas->isForbiddenRasterCache());
    }
    
    auto subPics = fRecordCanvas->takeSubPics();
    float cost = fRecordCanvas->getDrawCost();
//    auto diffShape = fRecordCanvas->getPendingDifferenceShape();   
    
    delete fRecordCanvas;
    fRecordCanvas = nullptr;
    
    OH_Drawing_RecordCmdUtilsFinishRecording(fOHRecorder, &recordCmd);
    if (!recordCmd) {
        return nullptr;
    }

    return sk_make_sp<SkOHPicture>(fCullRect, noLimitDraw, recordCmd, fNowOHNode, std::move(subPics), cost,
                                   fDisableRecycleNode, pictureHash);
}

sk_sp<SkPicture> SkPictureRecorder::finishRecordingAsPictureWithCull(const SkRect& cullRect) {
    fCullRect = cullRect;
    return this->finishRecordingAsPicture();
}


void SkPictureRecorder::partialReplay(SkCanvas* canvas) const {
    RENDER_UNIMPLEMENTED;
    return;
}

sk_sp<SkDrawable> SkPictureRecorder::finishRecordingAsDrawable() {
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     fActivelyRecording = false;
//     fRecorder->restoreToCount(1);  // If we were missing any restores, add them now.
//
//     SkRecordOptimize(fRecord.get());
//
//     if (fBBH) {
//         AutoTArray<SkRect> bounds(fRecord->count());
//         AutoTMalloc<SkBBoxHierarchy::Metadata> meta(fRecord->count());
//         SkRecordFillBounds(fCullRect, *fRecord, bounds.data(), meta);
//         fBBH->insert(bounds.data(), meta, fRecord->count());
//     }
//
//     sk_sp<SkDrawable> drawable =
//          sk_make_sp<SkRecordedDrawable>(std::move(fRecord), std::move(fBBH),
//                                         fRecorder->detachDrawableList(), fCullRect);
//
//     return drawable;
}
